package com.fqy.gmall.product.service.impl;

import com.fqy.gmall.product.service.TestService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.apache.commons.lang.StringUtils;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * User: fqy
 * Date: 2021/9/22
 * Time: 18:25
 * Describe:  测试分布式锁和本地锁的区别
 * 本地锁的局限性！
 * 案例：
 * 获取缓存中的数据key = num
 * 判断缓存中的数据是否为空！
 * true:
 * 将数据+1 ，放入缓存！
 * false:
 * 返回！
 */
@Service
public class TestServiceImpl implements TestService {

//    @Autowired
//    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void testLock() {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
            String num = redisTemplate.opsForValue().get("num2");
            System.out.println(num);
            if (StringUtils.isEmpty(num)) {
                return;
            } else {
                int parseIntNum = Integer.parseInt(num);
                redisTemplate.opsForValue().set("num2", String.valueOf(++parseIntNum));
            }
        } finally {
            lock.unlock();
        }
//        String num = redisTemplate.opsForValue().get("num2");
//        System.out.println(num);
//        if (StringUtils.isEmpty(num)) {
//            return;
//        }else {
//            int parseIntNum = Integer.parseInt(num);
//            redisTemplate.opsForValue().set("num2", String.valueOf(++parseIntNum));
//        }

    }

    /**
     *  解决分布式锁：
     *  1.setnx:	当key 存在时，不生效，key 不存在时生效！ del key!
     *  问题：setnx刚好获取到锁，业务逻辑出现异常，导致锁无法释放
     *  解决：设置过期时间，自动释放锁。
     *      命令一：
     * 		expire age 10;	缺乏原子性!
     *      命令二：
     * 		setex k1 10 v1;
     * 		set k1 v1 ex 30 nx; 保证原子性！推荐！
     * 	问题：可能会导致误删锁！
     * 		使用UUID防止误删锁！
     * 	解决：setnx获取锁时，设置一个指定的唯一值（例如：uuid）；释放前获取这个值，判断是否自己的锁
     * 	2.
     */


    @Override
    public void testLock2() {
        // 方式一 从redis中获取锁,setnx
        // Boolean flag = this.redisTemplate.opsForValue().setIfAbsent("lock", "ok");
        // 设置过期时间
        // redisTemplate.expire("lock", 10, TimeUnit.SECONDS);
        // 方式二 set k1 v1 ex 3 nx; 保证原子性！推荐！
//        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent("lock", "ok", 3, TimeUnit.SECONDS);
        // 方式三 使用UUID防止误删锁！
        String uuid = UUID.randomUUID().toString();
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        System.out.println(flag);
        //flag 为 true 说明里面没有锁！  不存在   生效
        if (flag) {
            String num = redisTemplate.opsForValue().get("num2");
            if (StringUtils.isEmpty(num)) {
                return;
            }
            int parseIntNum = Integer.parseInt(num);
            redisTemplate.opsForValue().set("num2", String.valueOf(++parseIntNum));
            //注意释放锁
//            redisTemplate.delete("lock");
            //  判断是否是属于自己的uuid
            if (uuid.equals(redisTemplate.opsForValue().get("lock"))) {
                //index1 到这了。
                //del 释放锁！
                redisTemplate.delete("lock");
            }
        } else {
            //flag 为 false 存在 不生效！ del key! false
            try {
                Thread.sleep(200);
                //  自旋
                testLock2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 问题：删除锁没有保证原子性！
     * 解决：使用lua 脚本保证删除具有原子性！
     */
    @Override
    public void testLock3() {
        String uuid = UUID.randomUUID().toString();
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        System.out.println(flag);
        //flag 为 true 说明里面没有锁！  不存在   生效
        if (flag) {
            String num = redisTemplate.opsForValue().get("num2");
            if (StringUtils.isEmpty(num)) {
                return;
            }
            int parseIntNum = Integer.parseInt(num);
            redisTemplate.opsForValue().set("num2", String.valueOf(++parseIntNum));
            //注意释放锁
//            redisTemplate.delete("lock");
            //  定义一个lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            //  准备执行lua 脚本
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //  将lua脚本放入DefaultRedisScript 对象中
            redisScript.setScriptText(script);
            //  设置DefaultRedisScript 这个对象的泛型
            redisScript.setResultType(Long.class);
            //  执行删除
            redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
        } else {
            //flag 为 false 存在 不生效！ del key! false
            try {
                Thread.sleep(200);
                //  自旋
                testLock3();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * redisson
     */
    @Override
    public void testLock4() {
        //上锁
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            //  业务逻辑
            String num = redisTemplate.opsForValue().get("num2");
            System.out.println(num);
            if (StringUtils.isEmpty(num)) {
                return;
            } else {
                int parseIntNum = Integer.parseInt(num);
                redisTemplate.opsForValue().set("num2", String.valueOf(++parseIntNum));
            }
        } finally {
            //  解锁
            lock.unlock();
        }
    }

    @Override
    public String readLock() {
        //  创建一个对写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("anyLock");
        //  上读锁
        rwlock.readLock().lock(10,TimeUnit.SECONDS);
        //  执行业务逻辑
        String msg = this.redisTemplate.opsForValue().get("msg");
        //  返回数据
        return msg;
    }

    @Override
    public String writeLock() {
        //  创建一个对写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("anyLock");
        //  上读锁
        rwlock.writeLock().lock(10,TimeUnit.SECONDS);
        //  执行业务逻辑
        this.redisTemplate.opsForValue().set("msg",UUID.randomUUID().toString());
        //  返回数据
        return "写入完成....";
    }


}
